﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SharpSoft.Geometries
{
    /*
1：一般式:Ax+By+C=0(A、B不同时为0)【适用于所有直线】
  该直线斜率k=-(A/B)【当B=0时没有斜率】，b=-(C/B) 
A1/A2=B1/B2≠C1/C2←→两直线平行
A1/A2=B1/B2=C1/C2←→两直线重合
横截距a=-C/A
纵截距b=-C/B
2：点斜式:y-y0=k(x-x0) 【适用于不垂直于x轴的直线】
表示斜率为k，且过（x0,y0）的直线
3：截距式:x/a+y/b=1【适用于不过原点或不垂直于x轴、y轴的直线】
表示与x轴、y轴相交，且x轴截距为a，y轴截距为b的直线
4：斜截式:y=kx+b【适用于不垂直于x轴的直线】
表示斜率为k且y轴截距为b的直线
5：两点式:【适用于不垂直于x轴、y轴的直线】
表示过（x1,y1）和(x2,y2)的直线　
两点式
两点式
(y-y1)/(y2-y1)=(x-x1)/(x2-x1) (x1≠x2，y1≠y2)
6：交点式:f1(x,y) *m+f2(x,y)=0 【适用于任何直线】
表示过直线f1(x,y)=0与直线f2(x,y)=0的交点的直线
7：点平式:f(x,y) -f(x0,y0)=0【适用于任何直线】
表示过点（x0,y0）且与直线f（x,y）=0平行的直线
法线式
法线式
8：法线式：x·cosα+ysinα-p=0【适用于不平行于坐标轴的直线】
过原点向直线做一条的垂线段，该垂线段所在直线的倾斜角为α，p是该线段的长度
9：点向式：(x-x0)/u=(y-y0)/v (u≠0,v≠0)【适用于任何直线】
表示过点(x0,y0)且方向向量为（u,v ）的直线
10：法向式：a（x-x0）+b（y-y0）=0【适用于任何直线】
表示过点（x0，y0）且与向量（a，b）垂直的直线。
*/


    /// <summary>
    /// 表示平面中的直线AX+BY+C=0
    /// </summary>
    /// <remarks>
    /// 一般直线方程式：AX+BY+C=0
    /// 其中：A = Y2 - Y1
    /// B = X1 - X2
    /// C = X2 * Y1 - X1 * Y2
    /// </remarks>
    public struct StraightLine2D
    {
        /// <summary>
        /// 通过直线上已知的两个点构建直线
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        public StraightLine2D(Point2D p1, Point2D p2)
        {
            if (p1 == p2)
            {
                throw new Exception("不能使用相同的两个点构建直线。");
            }
            A = p2.Y - p1.Y;
            B = p1.X - p2.X;
            C = p2.X * p1.Y - p1.X * p2.Y;
        }
        /// <summary>
        /// 通过已知的系数A/B/C构建直线
        /// </summary>
        /// <param name="xA"></param>
        /// <param name="xB"></param>
        /// <param name="xC"></param>
        public StraightLine2D(double xA, double xB, double xC)
        {
            if (xA + xB == 0d)
            {
                throw new Exception("参数A和B不能同时为零。");
            }
            A = xA; B = xB; C = xC;
        }

        public double A { get; private set; }
        public double B { get; private set; }
        public double C { get; private set; }
        /// <summary>
        /// 获取表示X轴的直线
        /// </summary>
        public static StraightLine2D X_Axis
        {
            get
            {
                return new StraightLine2D(new Point2D(0, 0), new Point2D(1, 0));
            }
        }
        /// <summary>
        /// 获取表示Y轴的直线
        /// </summary>
        public static StraightLine2D Y_Axis
        {
            get
            {
                return new StraightLine2D(new Point2D(0, 0), new Point2D(0, 1));
            }
        }
        /// <summary>
        /// 无效的直线
        /// </summary>
        public static StraightLine2D NaL = new StraightLine2D();
        /// <summary>
        /// 是否为无效的直线
        /// </summary>
        /// <returns></returns>
        public bool IsNaL()
        {
            if (double.IsNaN(A) || double.IsNaN(B) || double.IsNaN(C))
            {
                return true;
            }
            if (A + B == 0)
            {
                return true;
            }
            return false;
        }
        #region 常见算法

        /// <summary>
        /// 判断点是否在直线上
        /// </summary>
        /// <param name="point2D"></param>
        /// <returns></returns>
        public bool Exists(Point2D point2D)
        {
            return A * point2D.X + B * point2D.Y + C == 0;
        }
        /// <summary>
        /// 计算点到直线的垂直距离（最短距离）
        /// </summary>
        /// <returns></returns>
        public double DistanceToPoint(Point2D point)
        {
            /*
             * 点P(x0,y0)到直线Ι:Ax+By+C=0的距离
             * d=|Ax0+By0+C|/√A^2+B^2
             */
            return Math.Abs(A * point.X + B * point.Y + C) / Math.Sqrt(Math.Pow(A, 2) + Math.Pow(B, 2));
        }
        /// <summary>
        /// 计算两条平行直线之间的距离,如果两条直线不平行则结果没有意义。
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public Double DistanceToStraightLine(StraightLine2D line)
        {
            /*
             Ax+By+C1=O Ax+By+C2=0 则
             这两条平行直线间的距离d为：
             d= 丨C1-C2丨/√（A^2+B^2)
             */
            return Math.Abs(line.C - this.C) / Math.Sqrt(Math.Pow(A, 2) + Math.Pow(B, 2));
        }
        /// <summary>
        /// 获取相对于此直线的对称点
        /// </summary>
        /// <param name="p1"></param>
        /// <returns></returns>
        public Point2D GetSymmetryPoint(Point2D p1)
        {
            /*
             ( x0-2A(Ax0+By0+C)/(A^2+B^2) ，y0-2B(Ax0+By0+C)/(A^2+B^2) )
             */
            var ab_sqrt = Math.Sqrt(Math.Pow(A, 2) + Math.Pow(B, 2));
            return new Point2D(p1.X - 2 * A * (A * p1.X + B * p1.Y + C) / ab_sqrt, p1.Y - 2 * B * (A * p1.X + B * p1.Y + C) / ab_sqrt);
        }
        /// <summary>
        /// 获取两条直线的交点（如果相交）
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public Point2D? GetCrossingPoint(StraightLine2D line)
        {
            /*
            直线l1：a1x + b1y + c1 = 0 直线l2：a2x + b2y + c2 = 0 交点坐标为
            ((b1c2 - b2c1) / (a1b2 - a2b1)，(a2c1 - a1c2) / (a1b2 - a2b1))
   */
            if (!this.CrossingWith(line))
            {//没有交点
                return null;
            }
            return new Point2D((B * line.C - C * line.B) / (A * line.B - B * line.A), (C * line.A - A * line.C) / (A * line.B - B * line.A));
        }
        /// <summary>
        /// 计算两条直线的转向角
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public Radian GetAngleTo(StraightLine2D line)
        {
            /*
             * k=-(A/B)
             * tanθ=(k2- k1）/(1+ k1k2）
             */
            var k1 = -(A / B);
            var k2 = -(line.A / line.B);
            return (Radian)Math.Atan((k2 - k1) / (1 + k1 * k2));
        }

        /// <summary>
        /// 判断两条直线是否相交
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public bool CrossingWith(StraightLine2D line)
        {
            return A * line.B != B * line.A;
        }
        /// <summary>
        /// 判断两条直线是否平行（包括重合）
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public bool ParallelWith(StraightLine2D line)
        {
            return A / line.A == B / line.B;
        }
        /// <summary>
        /// 判断两条直线是否重合
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public bool CoincideWith(StraightLine2D line)
        {
            return A / line.A == B / line.B && A / line.A == C / line.C;
        }
        /// <summary>
        /// 判断两条直线是否垂直
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public bool VerticalWith(StraightLine2D line)
        {
            return A * line.A + B * line.B == 0;
        }

        /// <summary>
        /// 已知Y坐标求X坐标
        /// </summary>
        /// <param name="y"></param>
        /// <returns></returns>
        public double GetX(double y)
        {
            return (-(B * y + C)) / A;
        }
        /// <summary>
        /// 已知X坐标求Y坐标
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public double GetY(double x)
        {
            return (-(A * x + C)) / B;
        }

        #endregion

        #region 运算符重载

        /// <summary>
        /// 平移直线
        /// </summary>
        /// <param name="line"></param>
        /// <param name="vect"></param>
        /// <returns></returns>
        public static StraightLine2D operator +(StraightLine2D line, Vector2D vect)
        {
            return new StraightLine2D(line.A, line.B,
                        line.C - line.A * vect.X - line.B * vect.Y);
        }
         
        public static Point2D operator *(StraightLine2D line1, StraightLine2D line2)
        {
            double den = line1.A * line2.B - line2.A * line1.B;

            return new Point2D((line1.B * line2.C - line2.B * line1.C) / den,
                             (line2.A * line1.C - line1.A * line2.C) / den);
        }

        #endregion
        public override bool Equals(object obj)
        {
            if (obj is StraightLine2D l2)
            {
                return l2.A == A && l2.B == B && l2.C == C;
            }

            return false;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return $"LINE:[{A}*X+{B}*Y+{C}=0]";
        }
    }
}
